package com.skivingcloud.admin.security;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Collections;
import java.util.List;

/**
 * @author hushouquan
 */
@Configuration
@EnableMethodSecurity(securedEnabled = true)
@EnableWebSecurity
public class SecurityConfig{
    
    @Value("${spring.profiles.active}")
    private String active;
    @Value("${skivingcloud.common.feign.accessToken:''}")
    private String feignAccessToken;
    /**
     * 接口文档放行，生产阶段建议去掉
     */
    public static final List<String> DOC_WHITE_LIST = List.of("/doc.html", "/webjars/**", "/v3/api-docs/**", "/commonExample/**");
    
    /**
     * 监控接口
     */
    public static final List<String> ACTUATOR_LIST = List.of("/actuator/**");
    /**
     * 验证码放行
     */
    public static final List<String> VERIFY_CODE_WHITE_LIST = List.of("/sys/auth/captcha.jpg");
    
    /**
     * 登录、登出放行
     */
    public static final List<String> LOGIN_LIST = List.of("/sys/auth/login", "/sys/auth/logout");

    /**
     * 获取AuthenticationManager（认证管理器），登录时认证使用
     */
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }
    
    /**
     * 允许抛出用户不存在的异常
     * @param myUserDetailsService myUserDetailsService
     * @return DaoAuthenticationProvider
     */
    @Bean
    public DaoAuthenticationProvider daoAuthenticationProvider(MyUserDetailsServiceImpl myUserDetailsService) {
        final DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(myUserDetailsService);
        provider.setUserDetailsPasswordService(myUserDetailsService);
        provider.setHideUserNotFoundExceptions(false);
        provider.setPasswordEncoder(passwordEncoder());
        return provider;
    }
    
    @Bean
    public UsernameNoPasswordAuthenticationFilter noPasswordFilter(AuthenticationManager authenticationManager, MyAuthenticationHandler authenticationHandler, MyRememberMeServices rememberMeServices){
        return new UsernameNoPasswordAuthenticationFilter(authenticationManager, authenticationHandler, rememberMeServices);
    }
    
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http,
                                                   MyLoginFilter loginFilter,
                                                   UsernameNoPasswordAuthenticationFilter noPasswordFilter,
                                                   MyAuthenticationHandler authenticationHandler,
                                                   MyRememberMeServices rememberMeServices,
                                                   PersistentTokenRepository myTokenRepository) throws Exception {
        if("prod".equals(active)){
            http.authorizeHttpRequests(auth -> auth.requestMatchers(HttpMethod.GET, VERIFY_CODE_WHITE_LIST.toArray(new String[0])).permitAll()
                            .requestMatchers(HttpMethod.POST, LOGIN_LIST.toArray(new String[0])).permitAll()
                            .requestMatchers(ACTUATOR_LIST.toArray(new String[0])).permitAll()
                            .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                            .requestMatchers(request -> feignAccessToken.equals(request.getHeader("feignAccessToken"))).permitAll()
                            .anyRequest().authenticated())
//                .cors(cors -> cors.configurationSource(configurationSource()))
                    .addFilterAt(loginFilter, UsernamePasswordAuthenticationFilter.class)
                    .logout(logout -> logout.logoutUrl("/sys/auth/logout").logoutSuccessHandler(authenticationHandler))
                    .csrf(AbstractHttpConfigurer::disable)
                    .sessionManagement(session -> session.maximumSessions(1).expiredSessionStrategy(authenticationHandler))
                    .rememberMe(rememberMe -> rememberMe.tokenRepository(myTokenRepository).rememberMeServices(rememberMeServices).key("INTERNAL_SECRET_KEY").tokenValiditySeconds(86400))
                    .exceptionHandling(exception -> exception.accessDeniedHandler(authenticationHandler).authenticationEntryPoint(authenticationHandler))
                    .httpBasic(Customizer.withDefaults())
                    .headers(headersCustomizer -> {

                    });
        }else{
            http.authorizeHttpRequests(auth -> auth.requestMatchers(HttpMethod.GET, DOC_WHITE_LIST.toArray(new String[0])).permitAll()
                            .requestMatchers(HttpMethod.GET, VERIFY_CODE_WHITE_LIST.toArray(new String[0])).permitAll()
                            .requestMatchers(HttpMethod.POST, LOGIN_LIST.toArray(new String[0])).permitAll()
                            .requestMatchers(ACTUATOR_LIST.toArray(new String[0])).permitAll()
                            .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                            .requestMatchers(request -> feignAccessToken.equals(request.getHeader("feignAccessToken"))).permitAll()
                            .anyRequest().authenticated())
//                .cors(cors -> cors.configurationSource(configurationSource()))
                    .addFilterAt(loginFilter, UsernamePasswordAuthenticationFilter.class)
                    .logout(logout -> logout.logoutUrl("/sys/auth/logout").logoutSuccessHandler(authenticationHandler))
                    .csrf(AbstractHttpConfigurer::disable)
                    .sessionManagement(session -> session.maximumSessions(1).expiredSessionStrategy(authenticationHandler))
                    .rememberMe(rememberMe -> rememberMe.tokenRepository(myTokenRepository).rememberMeServices(rememberMeServices).key("INTERNAL_SECRET_KEY").tokenValiditySeconds(86400))
                    .exceptionHandling(exception -> exception.accessDeniedHandler(authenticationHandler).authenticationEntryPoint(authenticationHandler))
                    .httpBasic(Customizer.withDefaults());
        }
        return http.build();
    }
    
    //网关做了跨域处理所以这里不用
    CorsConfigurationSource configurationSource(){
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowedHeaders(Collections.singletonList("*"));
        corsConfiguration.setAllowedMethods(Collections.singletonList("*"));
        corsConfiguration.setAllowedOriginPatterns(Collections.singletonList("*"));
        corsConfiguration.setAllowCredentials(true);
        corsConfiguration.setMaxAge(3600L);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", corsConfiguration);
        return source;
    }
    
    //自定义加密方式
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
